import java.io.IOException;
import java.util.Timer;
import java.util.Vector;
import java.util.Random;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.media.Manager;
import javax.microedition.media.Player;

/**
 *
 * @author  Karel Herink
 * @version 1.0
 */
public class GameScreen extends GameCanvas implements Runnable, CommandListener {
    private boolean isPlay;
    boolean isPause = false;
    boolean gameOver = false;
    boolean isWinThisLevel = false;
    boolean isPlaySound = true;
    private LayerManager layerManager;
    private MushroomGameBuilder gameDesign;
    
    private Timer timer;
    public static Random random;
    private final int deltaTime = 20;
    Player player;
    int lap = -1;
    Vector spiders;
    
    Boy boy;
    Map map;
    Vector bullets;
    
    Graphics g;
    private Display display;
    
    //Gui
    String score;
    String hp;
    String mushroom;
    String gameover;
    
    //new property
    private GameMidlet midlet;  // Hold the Main Midlet 
    private Settings settings;  // Hold Game Settings 
    private HighScore highScore;        // Hold Game HighScore 
    private Command backCommand = new Command("Back", Command.BACK,1); 
    private Thread gameThread = null; 
    private int level;
    
    private int nmbSpider;
    private int spawnDelayTime;
    private boolean haveBoss;
    
    public MushroomGameBuilder getGameDesign() {
        return this.gameDesign;
    }
    
    public Timer getTimer() {
        return this.timer;
    }
    
    public Boy getBoy() {
        return this.boy;
    }
    
    public Vector getSpiders() {
        return this.spiders;
    }
    
    public Vector getBullets() {
        return this.bullets;
    }
    
    public Map getMap() {
        return this.map;
    }
    
    public LayerManager getLayerManager() {
        return this.layerManager;
    }
    
    public int getDeltaTime() {
        return deltaTime;
    }

    public GameScreen(GameMidlet midlet,Settings settings,HighScore score, int level) throws IOException, Exception {
        super(true);
        try {
            this.setFullScreenMode(true);
            this.init(midlet, settings, score);
            if (level != Level.Load)
                loadLevel(level);
            else {
                LoadSavedLevel();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    // Start thread for game loop 
    public void start() { 
        gameThread = new Thread(this); 
        gameThread.start(); 
        this.isPlay = true;
        
    }
    // Stop thread for game loop 
    public void stop() { 
        gameThread = null; 
        this.isPlay = false;
    }
    
    
    public void setDisplay(Display d) {
        this.display = d;
    }

    /**
     * Initialize the Game Design, then load all layers and start animation threads.
     */
    private void init(GameMidlet midlet, Settings settings, HighScore highScore) throws IOException, Exception {
        this.midlet = midlet;
        this.settings = settings;
        this.highScore = highScore;
        
        addCommand(backCommand); 
        setCommandListener(this);
        
        this.timer = new Timer();
        
        random = new Random();
        random.setSeed(System.currentTimeMillis());
        g = getGraphics();
        
        if(this.settings.getSound() == 0)
            this.isPlaySound = false;
        else this.isPlaySound = true;
        this.isPause = false;
        this.isPlay = false;
        this.gameOver = false;
        this.isWinThisLevel = false;
        
        this.gameDesign = new MushroomGameBuilder();
        this.layerManager = new LayerManager();
    }

    /**
     * The main game loop that checks for user input and repaints canvas.
     */
    public void run() {
        while (this.isPlay) {
            if (!gameOver) {
                if(this.lap != 400 && this.isPlaySound)
                {
                    try {
                    player = Manager.createPlayer(getClass().getResourceAsStream("/begin.wav"),"audio/x-wav");
                    player.start();

                    }catch(Exception e){
                    e.printStackTrace();
                    }
                }
                if(this.isPlaySound)
                    this.lap = player.getState();
                // Update world (boy, spider, map, move bullets, check for collisions)
                updateWorld();
            }

            // Do drawing (draw the current game state to the back buffer)
            drawWorld();

            // Give other threads a chance to run
            // Of course a real game should measure the time and between
            // each run of the game loop and update the world accordingly.
            // This version will run at a different speed on every handset.
            try {
                Thread.sleep(deltaTime);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
    
    private void updateWorld() {
        if (!this.isPause) {
            this.boy.update();
            for (int i = 0; i < this.spiders.size(); i++) {
                ((Spider)this.spiders.elementAt(i)).update();
            }
            for (int i = 0; i < this.bullets.size(); i++) {
                ((Bullet)this.bullets.elementAt(i)).update();
            }
        }
        this.update();
    }

    private void drawWorld() {
        
        this.layerManager.paint(g, 0, 0);
        
        this.draw();
        
        flushGraphics(0, 0, this.getWidth(), this.getHeight());
    }

    /*
     * GUI update
     */
    private void update() {
        score = "Score: " + this.boy.getScore();
        hp = "HP: " + this.boy.getHealth();
        mushroom = "Mushroom: " + this.boy.getMushroom();
    }

    /*
     * Draw GUI
     */
    private void draw() {
        //g = getGraphics();
        if (!gameOver) {
            // Draw score text
            g.setColor(255, 255, 255);
            g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE));
            g.drawString(score, 10, 10, Graphics.TOP | Graphics.LEFT);
            
            // Draw score text
            g.setColor(255, 255, 255);
            g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE));
            g.drawString(hp, 10, 30, Graphics.TOP | Graphics.LEFT);
            
            // Draw score text
            g.setColor(255, 255, 255);
            g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE));
            g.drawString(mushroom, 10, 50, Graphics.TOP | Graphics.LEFT);
        }
        else {
            // Draw game over text
            g.setColor(255, 255, 255);
            g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE));
            g.drawString(gameover, 10, 10, Graphics.TOP | Graphics.LEFT);
        }
    }

    public void addSpider() throws IOException, Exception {
        int nmbSpidersNeedSpawn = this.nmbSpider - this.spiders.size();
        
        if (map.getNumberSpawnPoint() < nmbSpidersNeedSpawn) {
            throw new Exception("Can't spawn too much spider!");
        }
        
        Vector randomPicker = new Vector();
        for (int i = 0; i < map.getNumberSpawnPoint(); i++) {
            randomPicker.addElement(map.getSpawnPoint(i));
        }

        for (int i = 0; i < nmbSpidersNeedSpawn; i++) {
            int nro = Math.abs(this.random.nextInt());
            int number = nro % randomPicker.size();

            Point randomSpawnPoint = (Point)randomPicker.elementAt(number);
            randomPicker.removeElementAt(number);
        
            this.spiders.addElement(createSpider(new MovingData(randomSpawnPoint, this.getRandomDirection(), GameCanvas.LEFT)));
        }
    }
    
    public void addBullet() throws IOException, Exception {
        MovingData movingData = new MovingData(new Point(boy.getX(), boy.getY()), boy.getLastDirection(), GameCanvas.LEFT);
        this.bullets.addElement(createBullet(movingData));
    }

    void gameOver(boolean isWin) {
        gameOver = true;
        if (isWin) {
            gameover = "YOU WIN!";
            if(this.isPlaySound)
            {
                try {
                Player player = Manager.createPlayer(getClass().getResourceAsStream("/win.wav"),"audio/x-wav");
                player.start();

                } catch(Exception e) {
                e.printStackTrace();
                }
            }
        }
        else {
            gameover = "GAME OVER!";
            if(this.isPlaySound)
            {
                try {
                Player player = Manager.createPlayer(getClass().getResourceAsStream("/gameover.wav"),"audio/x-wav");
                player.start();

                } catch(Exception e) {
                e.printStackTrace();
                }
            }
        }
        pauseGame();
    }

    void removeSpider(Spider aThis) throws IOException, Exception {
        this.spiders.removeElement(aThis);
        
        //TODO: delay beetween spider die and new spider created;
        addSpider();
    }

    void removeBullet(Bullet aThis) {
        this.bullets.removeElement(aThis);
    }

    private void pauseGame() {
        this.boy.stop();
        
        for (int i = 0; i < this.spiders.size(); i++) {
            ((Spider)this.spiders.elementAt(i)).stop();
        }
        
        for (int i = 0; i < this.bullets.size(); i++) {
            ((Bullet)this.bullets.elementAt(i)).stop();
        }
        
        this.map.stop();
    }
    
    private void unpauseGame() {
        this.boy.start();
        
        for (int i = 0; i < this.spiders.size(); i++) {
            ((Spider)this.spiders.elementAt(i)).start();
        }
        
        for (int i = 0; i < this.bullets.size(); i++) {
            ((Bullet)this.bullets.elementAt(i)).start();
        }
        
        this.map.start();
    }

    void mainScreenShow() {
        display.setCurrent(this);
    }

    private void restart() throws IOException, Exception {
        this.boy.restart();
        removeAllSpider();
        removeAllBullet();
        addSpider();
        
        this.isPause = false;
        unpauseGame();
        gameOver = false;
    }

    private void removeAllSpider() {
        Vector temp = this.spiders;
        for (int i = 0; i < temp.size(); i++) {
            ((Spider)temp.elementAt(i)).die();
        }
    }

    private void removeAllBullet() {
        Vector temp = this.bullets;
        for (int i = 0; i < temp.size(); i++) {
            ((Bullet)temp.elementAt(i)).start();
        }
    }

    public void commandAction(Command c, Displayable d) {
        if (c == backCommand) {
            try {
                autoSave();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            midlet.mainMenuScreenShow(null); 
        }
    }

    Sprite getBoySprite() throws IOException {
        return this.gameDesign.getBoy();
    }

    private Object createSpider(MovingData movingData) throws IOException {
        Sprite sprite = gameDesign.getSpiderNotSingleton();
        
        //define the reference in the midle of sprites frame so that transformations work well
        sprite.defineReferencePixel(8, 8);
        SpriteAnimationTask animator = new SpriteAnimationTask(sprite, true);
        animator.setMoving(true);
        timer.scheduleAtFixedRate(animator, 0, gameDesign.SpiderBackwardForwardWalkDelay);
        
        sprite.setPosition(movingData.point.x, movingData.point.y);
        sprite.setVisible(true);
        layerManager.insert(sprite, 0);
        
        Spider spider = new Spider(this, sprite, animator);
        spider.setDirection(movingData.direction);
        spider.setController(new SpiderController(spider));
        spider.setSequences(
            gameDesign.SpiderBackwardForwardWalk, Sprite.TRANS_NONE, 
            gameDesign.SpiderBackwardForwardWalk, Sprite.TRANS_ROT180, 
            gameDesign.SpiderLeftRightWalk, Sprite.TRANS_ROT180, 
            gameDesign.SpiderLeftRightWalk, Sprite.TRANS_NONE
            );
        
        return spider;
    }

    private Object createBullet(MovingData movingData) throws IOException {
        Sprite sprite = this.gameDesign.getBulletNotSingleton();
        //define the reference in the midle of sprites frame so that transformations work well
        sprite.defineReferencePixel(8, 8);
        SpriteAnimationTask animator = new SpriteAnimationTask(sprite, true);
        animator.setMoving(true);
        timer.scheduleAtFixedRate(animator, 0, gameDesign.Bulletseq001Delay);
        
        sprite.setPosition(movingData.point.x, movingData.point.y);
        sprite.setVisible(true);
        layerManager.insert(sprite, 0);
        
        Bullet bullet = new Bullet(this, sprite, animator);
        bullet.setDirection(movingData.direction);
        bullet.setController(new BulletController(bullet));
        if(this.isPlaySound)
        {
            try {
                Player player = Manager.createPlayer(getClass().getResourceAsStream("/fire.wav"),"audio/x-wav");
                player.start();

            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        return bullet;
    }

    private void createBoy() throws IOException {
        Sprite boySprite = this.gameDesign.getBoy();
        //define the reference in the midle of sprites frame so that transformations work well
        boySprite.defineReferencePixel(8, 8);
        SpriteAnimationTask boyAnimator = new SpriteAnimationTask(boySprite, false);
        timer.scheduleAtFixedRate(boyAnimator, 0, gameDesign.BoyWalkLeftRightDelay);
        boySprite.setPosition(map.home.x * map.tlThings.getCellWidth(), map.home.y* map.tlThings.getCellHeight());
        boy = new Boy(this, boySprite, boyAnimator);
        boy.setSequences(
            gameDesign.BoyWalkUpDown, Sprite.TRANS_NONE, 
            gameDesign.BoyWalkUpDown, Sprite.TRANS_NONE, 
            gameDesign.BoyWalkLeftRight, Sprite.TRANS_MIRROR, 
            gameDesign.BoyWalkLeftRight, Sprite.TRANS_NONE
            );
        this.boy.setController(new BoyController(this.boy));
    }

    private int getRandomDirection() {
        int returnDirection = 0;
        
        int nro = Math.abs(random.nextInt());
        int number = nro % 4;

        switch (number) {
            case 0: {
                returnDirection = GameCanvas.UP;
                break;
            }
            case 1: {
                returnDirection = GameCanvas.DOWN;
                break;
            }
            case 2: {
                returnDirection = GameCanvas.LEFT;
                break;
            }
            case 3: {
                returnDirection = GameCanvas.RIGHT;
                break;
            }
            default:
                break;
        }
        
        return returnDirection;
    }

    boolean spidersCollideWithBullet(Bullet bullet) throws IOException, Exception {
        boolean collide = false;
        for (int i = 0; i < spiders.size(); i++) {
            if (bullet.getSprite().collidesWith(((Spider)spiders.elementAt(i)).getSprite(), true)) {
                collide = true;
                ((Spider)spiders.elementAt(i)).die();
                boy.addScore(GameConfig.SCOREGIVENWHENSPIDERDIED);
                removeSpider((Spider)spiders.elementAt(i));
            }
        }
        return collide;
    }
    
    boolean spidersCollideWithSpider(Spider spider) {
        boolean collide = false;
        for (int i = 0; i < spiders.size(); i++) {
            if (spiders.elementAt(i) != spider) {
                if (spider.getSprite().collidesWith(((Spider)spiders.elementAt(i)).getSprite(), true)) {
                    collide = true;
                }
            }
        }
        return collide;
    }
    
    boolean spidersCollideWithBoy() {
        boolean collide = false;
        for (int i = 0; i < spiders.size(); i++) {
            if (boy.getSprite().collidesWith(((Spider)spiders.elementAt(i)).getSprite(), true)) {
                collide = true;
                ((Spider)spiders.elementAt(i)).turnOtherWay();
            }
        }
        return collide;
    }

    boolean boyCollideWithSpider(Spider spider) {
        boolean collide = spider.getSprite().collidesWith(boy.getSprite(), true);
        if (collide) {
            boy.addHealth(GameConfig.DAMAGEVALUE);
            boy.addScore(GameConfig.SCORELOSTWHENCOLLIDESPIDER);
        }
        return collide;
    }

    int getOtherDirection(int direction) {
        int returnDirection = 0;
        do {
            int nro = Math.abs(random.nextInt());
            int number = nro % 4;

            switch (number) {
                case 0: {
                    returnDirection = GameCanvas.UP;
                    break;
                }
                case 1: {
                    returnDirection = GameCanvas.DOWN;
                    break;
                }
                case 2: {
                    returnDirection = GameCanvas.LEFT;
                    break;
                }
                case 3: {
                    returnDirection = GameCanvas.RIGHT;
                    break;
                }
                default:
                    break;
            }
        }while (returnDirection == direction);
        
        return returnDirection;
    }

    void boyPickup(int thing, int indexX, int indexY) {
        switch (thing) {
            case ThingType.HEALTHBOTTLE: {
                boy.addHealth(GameConfig.HEALTHVALUE);
                boy.addScore(GameConfig.SCOREWHENCOLLIDETHING);
                map.setThing(indexX, indexY, ThingType.NONE);
                break;
            }
            case ThingType.MUSHROOM: {
                boy.addMushroom(1);
                boy.addScore(GameConfig.SCOREWHENCOLLIDETHING);
                map.setThing(indexX, indexX, ThingType.NONE);
                break;
            }
            case ThingType.HOME: {
                if (boy.getCanGoHome()) {
                    boy.goHome();
                }
                break;
            }
            default:
                break;
        }
    }

    private void loadLevel(int level) throws IOException, Exception {
        switch (level) { 
            case Level.Lake:  
                this.map = new Lake(this);
                gameDesign.updateLayerManagerForLake(layerManager);
            break; 
            case Level.Desert:
                this.map = new Desert(this);
                gameDesign.updateLayerManagerForDesert(layerManager);
            break; 
            case Level.Forest:
                this.map = new Forest(this);
                gameDesign.updateLayerManagerForForest(layerManager);
            break;
      }
        
        map.createSpawnPoint();
        this.nmbSpider = map.getNumberSpawnPoint() - (Difficult.Hard - this.settings.getDifficulty());
        this.spawnDelayTime = 1000 * (Difficult.Hard -this.settings.getDifficulty());
        this.haveBoss = this.settings.getDifficulty()==Difficult.Hard?true:false;
        
        bullets = new Vector();
        
        //0
        spiders = new Vector();
        
        //2
        addSpider();
        
        //4 Boy
        createBoy();
    }

    private void LoadSavedLevel() throws IOException, Exception {
        //create new map from maptype in database
        PlayingData data = new PlayingData(); 
        data.loadPlayingData();
        level = data.getMapType();
        
        switch (level) { 
            case Level.Lake:  
                this.map = new Lake(this);
                gameDesign.updateLayerManagerForLake(layerManager);
            break; 
            case Level.Desert:
                this.map = new Desert(this);
                gameDesign.updateLayerManagerForDesert(layerManager);
            break; 
            case Level.Forest:
                this.map = new Forest(this);
                gameDesign.updateLayerManagerForForest(layerManager);
            break;
      }
        
        map.loadMap();
        map.createSpawnPoint();
        
        int nmbSpiderLoaded = data.getNmbSpider();
        for (int i = 0; i < nmbSpiderLoaded; i++) {
            MovingData movingData = data.LoadSpiderMovingData(i);
            this.spiders.addElement(createSpider(movingData));
        }
        
        int nmbBulletLoaded = data.getNmbBullet();
        for (int i = 0; i < nmbBulletLoaded; i++) {
            MovingData movingData = data.LoadBulletMovingData(i);
            this.bullets.addElement(createBullet(movingData));
        }
        
        this.spawnDelayTime = data.getSpawnDelayTime();
        this.haveBoss = data.isHaveBoss();
        
        if (this.haveBoss) {
            //Load Boss
        }
        
        createBoy();
        boy.loadBoy();
    }

    private void autoSave() throws Exception {
        map.updateData();
        
        PlayingData data = new PlayingData();
        data.setMapType(level);
        
        data.setNmbSpider(this.spiders.size());
        Vector listSpiderMovingData = new Vector();
        for (int i = 0; i < this.spiders.size(); i++) {
            Point point = new Point(((Spider)this.spiders.elementAt(i)).getX(), ((Spider)this.spiders.elementAt(i)).getY());
            int direction = ((Spider)this.spiders.elementAt(i)).direction;
            int previousDirection = ((Spider)this.spiders.elementAt(i)).previousDirection;
            listSpiderMovingData.addElement(new MovingData(point, direction, previousDirection));
        }
        data.setSpiderMovingData(listSpiderMovingData);
        
        data.setNmbBullet(this.bullets.size());
        Vector listBulletMovingData = new Vector();
        for (int i = 0; i < this.bullets.size(); i++) {
            Point point = new Point(((Bullet)this.spiders.elementAt(i)).getX(), ((Spider)this.spiders.elementAt(i)).getY());
            int direction = ((Bullet)this.spiders.elementAt(i)).direction;
            int previousDirection = ((Bullet)this.spiders.elementAt(i)).previousDirection;
            listBulletMovingData.addElement(new MovingData(point, direction, previousDirection));
        }
        data.setBulletMovingData(listBulletMovingData);
        
        data.setSpawnDelayTime(this.spawnDelayTime);
        data.setHasBoss(haveBoss);
        
        data.updateData();
        
        boy.updateData();
    }
}
